﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Calculator
{
    public partial class Calculator : Form
    {
        #region member variables

        private Int32 mi_Operand1 = 0;
        private Int32 mi_Operand2 = 0;
        private Int32 mi_Result = 0;

        private Char mc_Operator;
        private Boolean mb_Operand1Set = false;
        private Boolean mb_OperatorSet = false;
        #endregion

        #region construcotrs
        public Calculator()
        {
            InitializeComponent();
        }
        #endregion

        #region methods


        /*  
         *  Captures KeyUp events from the form (which is set to handle all keyboard events via the)
         *  KeyPreview property.
         *  Ignores non-numeric, non-command keys (i.e. only 0-9, '\','*','-','+', etc are accepted).
         * 
         */
        private void OnKeyUp(object sender, KeyEventArgs e)
        {
            //  Number row or numpad keys
            if((e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) || (e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9))
            {
                txtOutput.Text += ConvertToChar(Int16.Parse(e.KeyValue.ToString()));
            }
            //  'Submit' keys
            else if(e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return)
            {
                try
                {
                    Calculate();
                }
                //  Just in case...don't let the app explode on a buffer overflow
                catch (OverflowException ex)
                {
                    ShowOverflowError();
                }
            }
            //  Operator keys
            else if(e.KeyCode == Keys.Divide || e.KeyCode == Keys.Multiply || e.KeyCode == Keys.Add || e.KeyCode == Keys.Subtract)
            {
                SetOperator(ConvertToChar(Int16.Parse(e.KeyValue.ToString())));
            }
            //  Esc
            else if (e.KeyCode == Keys.Escape)
            {
                Clear();
            }

        }

        /* 
         *  Generic handler for MouseClick events on all buttons.  Determines course of action
         *  based on the name of the calling control.
         *  
         */

        private void OnClick(object sender, MouseEventArgs e)
        {
            //  Typecasting to Button allows us to acceess its methods and properties
            switch (((Button)sender).Name)
            {
                //  The outcome of all of these is same: set the text / operator to the Text property of the control (e.g. '9', '*')
                case "btn0"         :
                case "btn1"         :
                case "btn2"         :
                case "btn3"         :
                case "btn4"         :
                case "btn5"         :
                case "btn6"         :
                case "btn7"         :
                case "btn8"         :
                case "btn9"         :   txtOutput.Text += ((Button)sender).Text;
                                        break;
                case "btnAdd"       : 
                case "btnSubtract"  :
                case "btnMultiply"  :
                case "btnDivide"    :   SetOperator(Char.Parse(((Button)sender).Text));
                                        break;

                case "btnCalculate" :   try 
                                        { 
                                            Calculate(); 
                                        }
                                        catch (OverflowException ex) 
                                        { 
                                            ShowOverflowError();  // Just in case the user gets carried away
                                        }
                                        break;

                case "btnClear"     :   Clear();
                                        break;

                default             :   //  Do nothing
                                        break;
            }
        }


        /*
         *  Verify that both operands have values assigned and peform the calculation set by SetOperator.
         *  Set Operand 1's value to the result of the calculation for the next pass.
         *  Set Operand 2's flag to false.
         *
         */
        private void Calculate()
        {
            if (mb_Operand1Set && mb_OperatorSet)
            {
                mi_Operand2 = Int32.Parse(txtOutput.Text);
                switch (mc_Operator)
                {
                    // Avoid divide-by-zero errors or 'infinity' output
                    case '/':   if (mi_Operand1 == 0 || mi_Operand2 == 0)
                                {
                                    mi_Result = 0;
                                }
                                else
                                {
                                    mi_Result = mi_Operand1 / mi_Operand2;
                                }
                                break;

                    case '*':   mi_Result = mi_Operand1 * mi_Operand2;
                                break;

                    case '-':   mi_Result = mi_Operand1 - mi_Operand2;
                                break;

                    case '+':   mi_Result = mi_Operand1 + mi_Operand2;
                                break;
                }
                txtHistory.Text += String.Format("{0} {1} {2} = {3}", new object[4]{mi_Operand1, mc_Operator, mi_Operand2, mi_Result});
                Clear();
            }
            
        }

        // Determines what operation to use for the calculation.  Sets the first operand and its flag.
        private void SetOperator(Char c_Input)
        {
            if (txtOutput.Text != "")
            {
                mc_Operator = c_Input;
                mb_OperatorSet = true;

                mi_Operand1 = Int32.Parse(txtOutput.Text);
                mb_Operand1Set = true;

                txtOutput.Text = "";
            }
        }


        //  Reset the flag for Operand 1, clear the output textbox.
        private void Clear()
        {
            mb_Operand1Set = false;
            txtOutput.Text = "";
        }

        /*
         *  The KeysConverter class doesn't play nicely with the number pad (or I just suck at figuring
         *  out how to make it work).   
         *  
         *  This method maps the (ASCII?) keycode to the actual keyboard label for output to the screen
         *  as opposed to outputting the keycode--i.e. NumPad0 instead of '0'
         *  
         *  I imagine this method might not get along with non-US keyboards (i.e. Japanese, Cyrillic).
         *  Good thing this isn't a commercial application!
         *  
         */
        private Char ConvertToChar(Int16 iInput)
        {
            switch (iInput)
            {
                // These four don't seem to be working. I'm probably not capturing the
                // shift key or something.
                
                case 42:  return '*'; // Shift 8
                case 43:  return '+'; // Shift =
                case 45:  return '-'; // Keyboard -
                case 47:  return '/'; // Keyboard /

                //  Number row keys
                case 48:  return '0';
                case 49:  return '1';
                case 50:  return '2';
                case 51:  return '3';
                case 52:  return '4';
                case 53:  return '5';
                case 54:  return '6';
                case 55:  return '7';
                case 56:  return '8';
                case 57:  return '9';

                // Numpad number keys
                case 96:  return '0';
                case 97:  return '1';
                case 98:  return '2';
                case 99:  return '3';
                case 100: return '4';
                case 101: return '5';                case 102: return '6';
                case 103: return '7';
                case 104: return '8';
                case 105: return '9';

                // Numpad operator keys
                case 106: return '*';
                case 107: return '+';
                case 109: return '-';
                case 111: return '/';

                default: throw new ArgumentException("Only calculator characters, please");
            }
        }

        //  Called when an OverflowException has occurred.  Informs the user that they need to stay within Int32 bounds.
        private void ShowOverflowError()
        {
            MessageBox.Show(String.Format("Unable to perform calculation.\n {0} {1} {2} resulted in an OverflowException", mi_Operand1, mc_Operator, mi_Operand2), "Overflow Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }


        /*  
         *  Enable / disable styling on Output and History textboxes by setting Enabled / Disabled on 
         *  the corresponding controls.
         * 
         *  Also clears any applied styles.
         *  
         */

        private void ToggleStyling(object sender, EventArgs e)
        {
            if(((RadioButton)sender).Name == "rbStylingOn")
            {
                chkItalic.Enabled = true;
                chkUnderline.Enabled = true;
                chkBold.Enabled = true;
                nudFontSize.Enabled = true;
            }
            else
            {
                chkItalic.Enabled = false;
                chkItalic.Checked = false;

                chkUnderline.Enabled = false;
                chkUnderline.Checked = false;

                chkBold.Enabled = false;
                chkBold.Checked = false;

                nudFontSize.Enabled = false;
                nudFontSize.Value = 10;

                txtOutput.Font = new Font(txtOutput.Font.Name, 10, txtOutput.Font.Style & 0, GraphicsUnit.Point);
                txtHistory.Font = new Font(txtOutput.Font.Name, 10, txtOutput.Font.Style & 0, GraphicsUnit.Point);
            }
        }


        //  Toggle Italic font style (Output and History textboxes)
        private void chkItalic_CheckedChanged(object sender, EventArgs e)
        {
            if (chkItalic.Checked)
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style | FontStyle.Italic);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style | FontStyle.Italic);
            }
            else
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style ^ FontStyle.Italic);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style ^ FontStyle.Italic);
            }
        }

        //  Toggle Underline font style (Output and History textboxes)
        private void chkUnderline_CheckedChanged(object sender, EventArgs e)
        {
            if (chkUnderline.Checked)
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style | FontStyle.Underline);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style | FontStyle.Underline);
            }
            else
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style ^ FontStyle.Underline);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style ^ FontStyle.Underline);
            }
        }

        //  Toggle Bold font style (Output and History textboxes)
        private void chkBold_CheckedChanged(object sender, EventArgs e)
        {
            if (chkBold.Checked)
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style | FontStyle.Bold);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style | FontStyle.Bold);
            }
            else
            {
                txtOutput.Font = new Font(txtOutput.Font, txtOutput.Font.Style ^ FontStyle.Bold);
                txtHistory.Font = new Font(txtHistory.Font, txtHistory.Font.Style ^ FontStyle.Bold);
            }
        }

        //  Adjust font size based on nudFontSize's value.
        private void nudFontSize_ValueChanged(object sender, EventArgs e)
        {
            txtOutput.Font = new Font(txtOutput.Font.Name, (float)((NumericUpDown)sender).Value, txtOutput.Font.Style, GraphicsUnit.Point);
            txtHistory.Font = new Font(txtOutput.Font.Name, (float)((NumericUpDown)sender).Value, txtOutput.Font.Style, GraphicsUnit.Point);
        }

        #endregion
      
    }
}
